/*-------------------------------------------------------------------------
 *
 * bgwriter.c
 *
 * 背景写入器（bgwriter）是 Postgres 8.0 新增的。它尝试保持常规后端不必写出脏共享缓冲区（它们只会在需要释放共享缓冲区以读取另一个页面时才会这样做）。在最佳情况下，所有来自共享缓冲区的写入将由背景写入器进程发出。然而，如果 bgwriter 未能维护足够的干净共享缓冲区，常规后端仍然能够发出写入。
 *
 * 自 Postgres 9.2 起，bgwriter 不再处理检查点。
 *
 * 正常终止通过 SIGTERM 进行，这指示 bgwriter exit(0)。紧急终止通过 SIGQUIT 进行；像任何后端一样，bgwriter 在 SIGQUIT 时将简单地中止并退出。
 *
 * 如果 bgwriter 意外退出，postmaster 将其视为后端崩溃：共享内存可能已损坏，因此剩余的后端应通过 SIGQUIT 被终止，然后启动恢复周期。
 *
 *
 * 部分 Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/postmaster/bgwriter.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/bgwriter.h"
#include "postmaster/interrupt.h"
#include "storage/buf_internals.h"
#include "storage/bufmgr.h"
#include "storage/condition_variable.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lwlock.h"
#include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/shmem.h"
#include "storage/smgr.h"
#include "storage/spin.h"
#include "storage/standby.h"
#include "utils/guc.h"
#include "utils/memutils.h"
#include "utils/resowner.h"
#include "utils/timestamp.h"

/*
 * GUC参数
 */
int			BgWriterDelay = 200;

/*
 * 我们决定休眠时应用于 BgWriterDelay 的乘数。
 * （也许这需要可配置？）
 */
#define HIBERNATE_FACTOR			50

/*
 * 在该间隔内，备用快照记录到 WAL 流中，单位为毫秒。
 */
#define LOG_SNAPSHOT_INTERVAL_MS 15000

/*
 * LSN和上次发出LogStandbySnapshot()的时间戳，以避免
 * 过于频繁或重复进行，如果系统中没有其他写入活动。
 */
static TimestampTz last_snapshot_ts;
static XLogRecPtr last_snapshot_lsn = InvalidXLogRecPtr;


/*
 * bgwriter进程的主要入口点
 *
 * 这是从AuxiliaryProcessMain调用的，该函数已经创建了
 * 基本执行环境，但尚未启用信号。
 */
void BackgroundWriterMain(void)
{
	sigjmp_buf	fc_local_sigjmp_buf;
	MemoryContext fc_bgwriter_context;
	bool		fc_prev_hibernate;
	WritebackContext fc_wb_context;

	/*
	 * 正确接受或忽略可能发给我们的信号。
	 */
	pqsignal(SIGHUP, SignalHandlerForConfigReload);
	pqsignal(SIGINT, SIG_IGN);
	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
	/* SIGQUIT 处理程序已经由 InitPostmasterChild 设置 */
	pqsignal(SIGALRM, SIG_IGN);
	pqsignal(SIGPIPE, SIG_IGN);
	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
	pqsignal(SIGUSR2, SIG_IGN);

	/*
	 * 重置一些由postmaster接受但这里不接受的信号
	 */
	pqsignal(SIGCHLD, SIG_DFL);

	/*
	 * 我们刚刚启动，假设已经发生了关机或
	 * 恢复结束快照。
	 */
	last_snapshot_ts = GetCurrentTimestamp();

	/*
	 * 创建一个内存上下文，我们将所有工作都在这里完成。我们这样做是为了
	 * 在错误恢复期间重置上下文，从而避免
	 * 可能的内存泄漏。以前这段代码在
	 * TopMemoryContext中运行，但重置它会是个非常糟糕的主意。
	 */
	fc_bgwriter_context = AllocSetContextCreate(TopMemoryContext,
											 "Background Writer",
											 ALLOCSET_DEFAULT_SIZES);
	MemoryContextSwitchTo(fc_bgwriter_context);

	WritebackContextInit(&fc_wb_context, &bgwriter_flush_after);

	/*
	 * 如果遇到异常，处理将在这里恢复。
	 *
	 * 你可能会想为什么这不是在一个围绕
	 * PG_TRY构造的无限循环中编码的。原因是这是异常堆栈的底部，
	 * 因此在CATCH部分的过程中完全没有异常处理程序
	 * 生效。通过保持最外层的setjmp
	 * 始终处于活动状态，我们至少有一些机会在错误
	 * 恢复期间恢复错误。（如果由此陷入无限循环，它将很快因overflow
	 * elog.c的内部状态堆栈而停止。）
	 *
	 * 请注意，我们使用sigsetjmp(..., 1)，因此当长跳转到这里时，
	 * 当前信号掩码（即BlockSig）将被恢复。因此，
	 * 除了SIGQUIT之外的信号将被阻止，直到我们完成错误
	 * 恢复。这一政策看似使HOLD_INTERRUPTS()
	 * 调用变得多余，但实际上并非如此，因为InterruptPending可能
	 * 已经被设置。
	 */
	if (sigsetjmp(fc_local_sigjmp_buf, 1) != 0)
	{
		/* 由于未使用 PG_TRY，必须手动重置错误堆栈 */
		error_context_stack = NULL;

		/* 在清理期间防止中断 */
		HOLD_INTERRUPTS();

		/* 将错误报告到服务器日志 */
		EmitErrorReport();

		/*
		 * 这些操作实际上只是AbortTransaction()的一个最小子集。
		 * 我们在bgwriter中没有太多资源需要担心，
		 * 但我们确实有LWLocks、缓冲区和临时文件。
		 */
		LWLockReleaseAll();
		ConditionVariableCancelSleep();
		AbortBufferIO();
		UnlockBuffers();
		ReleaseAuxProcessResources(false);
		AtEOXact_Buffers(false);
		AtEOXact_SMgr();
		AtEOXact_Files(false);
		AtEOXact_HashTables(false);

		/*
		 * 现在返回到正常的顶级上下文并清除 ErrorContext，以便下次使用。
		 */
		MemoryContextSwitchTo(fc_bgwriter_context);
		FlushErrorState();

		/* 刷新顶级上下文中任何泄漏的数据 */
		MemoryContextResetAndDeleteChildren(fc_bgwriter_context);

		/* 重新初始化以避免重复错误造成问题 */
		WritebackContextInit(&fc_wb_context, &bgwriter_flush_after);

		/* 现在我们可以再次允许中断 */
		RESUME_INTERRUPTS();

		/*
		 * 在任何错误后至少睡眠1秒。写入错误很可能会
		 * 重复发生，我们不想尽可能快地填满错误日志。
		 */
		pg_usleep(1000000L);

		/*
		 * 在任何错误后关闭所有打开的文件。这对Windows很有帮助，
		 * 因为保持已删除的文件打开会导致各种奇怪的错误。
		 * 目前不清楚我们是否需要在其他地方这样做，但应该不会有坏处。
		 */
		smgrcloseall();

		/* 在没有进一步等待的可能性时报告等待结束 */
		pgstat_report_wait_end();
	}

	/* 我们现在可以处理 ereport(ERROR) */
	PG_exception_stack = &fc_local_sigjmp_buf;

	/*
	 * 解锁信号（它们在postmaster分叉我们时被阻止）
	 */
	PG_SETMASK(&UnBlockSig);

	/*
	 * 在任何错误后重置休眠状态。
	 */
	fc_prev_hibernate = false;

	/*
	 * 永远循环
	 */
	for (;;)
	{
		bool		fc_can_hibernate;
		int			fc_rc;

		/* 清除任何已经等待的唤醒 */
		ResetLatch(MyLatch);

		HandleMainLoopInterrupts();

		/*
		 * 执行一次脏缓存写入的循环。
		 */
		fc_can_hibernate = BgBufferSync(&fc_wb_context);

		/* 向累积统计系统报告待处理统计信息 */
		pgstat_report_bgwriter();
		pgstat_report_wal(true);

		if (FirstCallSinceLastCheckpoint())
		{
			/*
			 * 在任何检查点之后，关闭所有smgr文件。这是为了我们
			 * 不会无限期地保留对已删除文件的smgr引用。
			 */
			smgrcloseall();
		}

		/*
		 * 每隔一段时间记录新的xl_running_xacts，以便复制可以
		 * 更快地进入一致状态（想想溢出的快照）并更频繁地清理
		 * 资源（锁、KnownXids*）。这项操作的成本相对较低，
		 * 所以每分钟执行4次（LOG_SNAPSHOT_INTERVAL_MS）似乎没有问题。
		 *
		 * 我们假设写入xl_running_xacts的间隔
		 * 显著大于BgWriterDelay，因此我们不复杂化
		 * 整体超时处理，而只是假设即使在休眠模式下也会
		 * 足够频繁地调用我们。严格遵循LOG_SNAPSHOT_INTERVAL_MS
		 * 并不是那么重要。为了确保在空闲系统上
		 * 不必要地唤醒磁盘，我们检查自上次
		 * 记录正在运行的事务以来是否插入了任何WAL。
		 *
		 * 我们在bgwriter中执行此日志记录，因为这是唯一一个
		 * 定期运行并始终返回到其主循环的进程。例如，
		 * 当Checkpoint进程活动时，它几乎不在其主循环中，因此
		 * 很难进行定期日志记录。
		 */
		if (XLogStandbyInfoActive() && !RecoveryInProgress())
		{
			TimestampTz fc_timeout = 0;
			TimestampTz fc_now = GetCurrentTimestamp();

			fc_timeout = TimestampTzPlusMilliseconds(last_snapshot_ts,
												  LOG_SNAPSHOT_INTERVAL_MS);

			/*
			 * 仅在足够时间过去并且自上次快照以来插入了有趣的记录时进行日志记录。
			 * 必须使用<=而不是<进行比较，因为GetLastImportantRecPtr()指向
			 * 记录的开始，而last_snapshot_lsn指向
			 * 记录的结束后。
			 */
			if (fc_now >= fc_timeout &&
				last_snapshot_lsn <= GetLastImportantRecPtr())
			{
				last_snapshot_lsn = LogStandbySnapshot();
				last_snapshot_ts = fc_now;
			}
		}

		
/*
		 * 睡眠直到我们被信号唤醒或 BgWriterDelay 时间已到。
		 *
		 * 注意：BgBufferSync() 中的反馈控制循环期望我们每 BgWriterDelay 毫秒调用一次它。
		 * 尽管严格来说这并不是正确性的关键，但如果我们偏离太远，反馈循环可能会出现问题。
		 * 因此，避免在正常操作期间因频繁发生的 latch 事件给此进程增加负担。
		 */
		fc_rc = WaitLatch(MyLatch,
					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
					   BgWriterDelay /* 毫秒 */ , WAIT_EVENT_BGWRITER_MAIN);

		/*
		 * 如果没有 latch 事件且 BgBufferSync 表示没有发生任何事情，那么在“休眠”模式下
		 * 延长睡眠时间，此时我们睡眠的时间远超 bgwriter_delay 所规定的时间。减少唤醒可以节省电力。
		 * 当后端再次开始使用缓冲区时，将通过设置我们的 latch 来唤醒我们。
		 * 由于额外的睡眠时间仅在没有缓冲区分配发生时持续，因此这不应该严重扭曲
		 * BgBufferSync 的控制循环的行为；基本上，它会认为系统范围内的闲置间隔并不存在。
		 *
		 * 这里存在一个竞争条件，因为后端可能会在 BgBufferSync 看到分配计数为零
		 * 和我们调用 StrategyNotifyBgWriter 之间分配一个缓冲区。尽管我们并不一定不希望进入休眠状态，
		 * 但我们试图通过仅在 BgBufferSync 表示连续两个周期内没有发生任何事情时才进入休眠来降低这个机会。
		 * 此外，我们通过不永远休眠来减轻错过唤醒的任何可能后果。
		 */
		if (fc_rc == WL_TIMEOUT && fc_can_hibernate && fc_prev_hibernate)
		{
			/* 请求在下次缓冲区分配时通知 */
			StrategyNotifyBgWriter(MyProc->pgprocno);
			/* 睡眠... */
			(void) WaitLatch(MyLatch,
							 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
							 BgWriterDelay * HIBERNATE_FACTOR,
							 WAIT_EVENT_BGWRITER_HIBERNATE);
			/* 重置通知请求以防超时 */
			StrategyNotifyBgWriter(-1);
		}

		fc_prev_hibernate = fc_can_hibernate;
	}
}
